查看原文
其他

【冬瓜哥熊文】大话流水线(3终)~实际应用及优化

冬瓜哥 大话存储 2018-10-31

1. CPU流水线

在IT领域,流水线的最典型实际应用就是CPU内部执行机器指令的过程。比如某程序含有大量的机器指令,位于内存中,CPU需要逐条取回和处理。比如某条指令为add指令,CPU首先要从内存读出该指,然后要对其译码,也就是看看该指令到底是让我干什么,译码之后会向对应的电路部件发送控制指令执行该操作。如果是stor/load指令,则译码之后的结果是访问内存,而不是计算。可以看到,CPU内部的电路对一条机器指令的处理起码可以分为取指令、译码生成控制信号、计算(从ALU中选出对应的结果)、访问内存(如果是Load/Stor指令)、写回(将结果导入寄存器)这5个大步骤。电路完全可以把这5个步骤放在一起执行,也就是让电信号传递到所有的控制逻辑中,最终结果直接被导入到结果寄存器前端,然后在下一个时钟的边沿锁存该结果。每个时钟周期非常长,以便让电信号流经所有这些逻辑电路并输出结果。这么做的结果就是,某指令被取回之后,进入译码逻辑电路,此时取指令电路就会闲置在那里;译码完之后,比如是Load/Stor类指令,那么电路会向L1 Cache发送访存请求,此时取指令、译码电路一起闲在那,取指令闲的更久。同理,直到该指令的结果被输出到寄存器,下一个时钟边沿到来之前,CPU内大量的逻辑电路模块都处于闲置状态。对于这样一个系统,其能够执行的指令吞吐量,根据上文中总结出的算式,1/总时延,相当于1/时钟周期。该流水线相当于一个只有1级且该级时延=1个时钟周期的流水线,且时钟周期很长。其本质上等价于同步流水线。

这个步骤非常适合于改造为异步流水线,位于内存中的大量的机器指令就是待处理的角色;取指、译码等模块就是流水线的每一级,将原本一个大步骤切分为上述的5个小步骤,并且让这5个步骤的时延尽量缩小,而且让其异步执行。取指令、访存这两步的时延会比较高,因为可能需要访问外部SDRAM,译码时延相对低一些,但是相比计算步骤来讲可能也高一些,当然,看最终是计算什么,如果是加减乘那么会相当快,如果是除法则需要相当长的时间才能完成,而译码相对就简单了。正因如此,为了降低取指令和访存的时延,人们增加了L1 、L2、L3 Cache,让取指和访存尽量在Cache中命中;以及将译码阶段再次分割为各种预译码阶段,比如对指令进行定界(判断控制码、源操作寄存器、目标操作寄存器的长度)、检查指令码是否合法等细小的步骤。这样改造之后,每一步时延会非常小,而每一步都用一个时钟边沿来驱动和锁存结果,这样就需要把时钟频率提上去。此时该异步流水线的吞吐量依然为1/[时钟周期],但是时钟周期大大缩短,吞吐量就上去了。

切分为多个步骤之后,上级与下级之间就需要一个暂存上级输出结果的地方,结果被暂存到这里之后,上一级立即开始处理其自身的上游输出给它的结果(来自上游寄存器)。由于每一级都是组合逻辑电路,输入随着输出动态改变,所以每一级之间的这个暂存地应该是边沿触发寄存器。每个时钟边沿触发锁存上一级输出的结果,锁存之后,寄存器内的信号立即会对下一级组合逻辑电路产生影响,算出结果输送到下一级寄存器输入端等待。

实际设计时,每一步的时延不可能被设计的精确相等,所以时钟周期必须按照耗时最长的那一步来定,这样的话耗时短的就会先干完活,对应的信号会在其与下级步骤之间的寄存器输入端等待着,越早干完活的,等待时间就越长,这一级本身的闲置时间也就越长。

我们之前说过,在流水线各个级之间增加缓冲队列,非常有利于抗波动,保持吞吐量稳定。CPU的指令执行流水线不仅波动大,而且耦合性很大。其与上述介绍的处理物品的流水线不同,CPU内部流水线是将一条指令的处理切分为多个小步骤形成流水线,每一级处理的一条指令的一小步,而不是整条指令。指令之间可能存在很强的依赖性/耦合性/相关性,这会导致一系列问题,冬瓜哥就不多展开了。

 

2. 并行计算

如果上升到软件层面,程序处理也可以采用流水线思想来加速。比如某程序包含5千行代码,其处理时延就是CPU运行5千行代码所耗费的时间。现在要对该程序加速,那么采用流水线思想,将5千行代码的程序分割成5个程序,每个程序一千行代码,那么其理论吞吐量会翻5倍。

        但是不要忽略一个问题。对于物品传递流水线来讲,是真的有多个角色在同时工作从而传递物品,请注意“同时”这个词,同一个时刻,多级流水线处理模块都在工作。而对于程序来讲,要实现流水线,就得这多个程序同时在执行。如果多个程序在同一个CPU上运行,其就不是异步流水线,而本质上等价于同步流水线。所以,必须让流水线中的每个程序各自都在不同的CPU上同时运行,才可以达到增加吞吐量的效果。这就是所谓并行计算。此外,并不一定必须使用流水线思想才能增加吞吐量,可以直接复制出多个程序副本(俗称worker),让每个CPU都运行一份该程序,处理不同的数据(将数据切分为多份分别处理)即便该程序总时延很高,但是通过增加物理并发度,一样可以达到等价效果。

        当然,最终还得看场景,有些场景无法流水线化或者物理并行处理,因为数据之间耦合太紧密,比如:必须处理完上一份数据,才知道下一份数据应该如何处理,或者该从哪一步开始处理。这样就只能等待上一份数据完全出流水线后,由程序判断出结果,决定下一份数据进入流水线哪一级,比如直接跨过程序1、2而进入程序3处理。

对数据处理方面有些额外的内容,可以参考《大话众核心处理器体系结构》一文。

 

3. I/O系统

操作系统内核的I/O子系统处理的是应用程序下发的IO请求,对应的吞吐量描述用语是“IOPS”,每秒IO操作数。每一笔IO请求一般都需要经过:目录层、文件系统层、块层、SCSI/NVMe协议栈核心、HBA驱动、外部总线/网络、外部设备,外部设备本身也是一台小计算机,其内部也需要经过众多处理步骤。

操作系统一般会创建若干个内核线程来专门负责处理这些IO调用,但是普遍做法是一个线程把几乎所有上述这些步骤都处理完,但是是多个线程并行处理。IO请求在OS内核中的处理路径模型其实匹配了单级多物理并发流水线,理论吞吐量就是处理线程数/每个线程的处理总时延

但是IO请求经过OS内核处理之后,最终会被发送到外部设备来处理。从HBA控制器到外部设备这段路径上的时延,要远高于IO请求在内核中所经历的时延,即便是固态硬盘,时延相比内核处理时间也是很高。不管IO请求在HBA之外经历了多少级、多少并发度的流水线,可以肯定的是外部任何一级的吞吐量都要远低于OS内核的理论吞吐量(相当于内存的吞吐量),那么整体IO处理流水线的吞吐量,按照上文的分析,受限于吞吐量最低的那一级,一般来讲就是最终的硬盘(机械或者固态)。有个一个特例可以在外部流水线的某一级的吞吐量要高于OS内核处理吞吐量,那就是在IO路径上某处使用比如DDR SDRAM介质来虚拟成一个块设备,此时OS内核处理的吞吐量反而要低于这一级的理论吞吐量,但是无济于事,必定有某处的吞吐量依然会制约整体IO路径的吞吐量,除非整个路径都只访问内存。

    



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存